package util.parsing;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.xml.sax.InputSource;

import raytracing.Camera;
import scene.CubeMap;
import scene.Scene;
import scene.lighting.AreaLight;
import scene.lighting.Light;
import scene.scenegraph.SceneGraphNode;
import scene.scenegraph.ShapeNode;
import scene.scenegraph.TransformNode;
import scene.shape.geometry.Geometry;
import scene.shape.geometry.Plane;
import scene.shape.geometry.Primitive;
import scene.shape.geometry.Sphere;
import scene.shape.materials.Material;
import scene.shape.materials.PhongMaterial;
import scene.shape.materials.RefractionMaterial;
import scene.shape.materials.SpecularMaterial;
import scene.shape.materials.Texture;
import scene.shape.Shape;
import util.Color3f;
import util.Matrix4f;
import util.Point3f;
import util.TexCoord2f;
import util.Vector3f;

/**
  * Class used to build a scene from a given sdl file.
  * Implements the ParserHandler interface (these methods
  * need to be filled in by you).
  * 
  * Note that this class keeps the absolute path to the
  * directory where the sdl file was found.  If you put your
  * textures in the same directory, you can use this path
  * to construct an absolute file name for each texture.
  * You will probably need absolute file names when loading
  * the texture.
  */
public class SceneBuilder implements ParserHandler
{

    // the scene being build
    private Scene scene = null;

    private List<Geometry> geometries = new ArrayList<Geometry>();
    private List<Light> lights = new ArrayList<Light>();
    private List<Material> materials = new ArrayList<Material>();
    private Stack<Matrix4f> matrixStack = new Stack<Matrix4f>();
    private SceneGraphNode scenegraph = new TransformNode(Matrix4f.identity(),null);
    
    
    @SuppressWarnings("unused")
	private Scene getScene() { return scene; }

    // the path to the xml directory
    // this path can be used to put in front of the texture file name
    // to load the textures
    private String path = null;

    public String getPath() { return path; }


    /**
     * Load a scene.
     * @param filename The name of the file that contains the scene.
     * @return The scene, or null if something went wrong.
     * @throws FileNotFoundException The file could not be found.
     */
    public Scene loadScene(String filename) throws FileNotFoundException
    {
        // create file and file input stream
        File file = new File(filename);
        FileInputStream fileInputStream = new FileInputStream(file);

        // set the system id so that the dtd can be a relative path
        // the first 2 lines of your sdl file should always be
        //    <?xml version='1.0' encoding='utf-8'?>
        //    <!DOCTYPE Sdl SYSTEM "sdl.dtd">
        // and sdl.dtd should be in the same directory as the dtd
        // if you experience dtd problems, commend the doctype declaration
        //    <!-- <!DOCTYPE Sdl SYSTEM "sdl.dtd"> -->
        // and disable validation (see further)
        // although this is in general not a good idea

        InputSource inputSource = new InputSource(fileInputStream);
        //String parentPath = file.getParentFile().getAbsolutePath() + "/";
        path = file.getParentFile().getAbsolutePath() + "/";
        inputSource.setSystemId("file:///" + file.getParentFile().getAbsolutePath() + "/");



        // create the new scene
        scene = Scene.getInstance();

        // create the parser and parse the input file
        Parser parser = new Parser();
        parser.setHandler(this);

        // if the output bothers you, set echo to false
        // also, if loading a large file (with lots of triangles), set echo to false
        // you should leave validate to true
        // if the docuement is not validated, the parser will not detect syntax errors
        if (parser.parse(inputSource, /* validate */ true, /* echo */ true) == false)
        {
            scene = null;
        }

        // return the scene
        return scene;
    }

    /*
     *  (non-Javadoc)
     * ParserHandler callbacks
     */	

    public void startSdl() throws Exception
    {
    }

    public void endSdl() throws Exception
    {
    }

    public void startCameras() throws Exception
    {
    }

    public void endCameras() throws Exception
    {
    }

    public void startCamera(Point3f position, Vector3f direction, Vector3f up, float fovy, String name) throws Exception
    {
    	scene.addCamera(new Camera(new Vector3f(position), (new Vector3f(position)).add(direction), up, fovy, name));
    }

    public void endCamera() throws Exception
    {
    }

    public void startLights() throws Exception
    {
    }

    public void endLights() throws Exception
    {
    }

    public void startDirectionalLight(Vector3f direction, float intensity, Color3f color, String name) throws Exception
    {
    	
    }

    public void endDirectionalLight() throws Exception
    {
    }

    public void startPointLight(Point3f position, float intensity, Color3f color, String name) throws Exception
    {
    	lights.add(new Light(new Vector3f(position),intensity,color,name));
    }

    public void endPointLight() throws Exception
    {
    }

    public void startSpotLight(Point3f position, Vector3f direction, float angle, float intensity, Color3f color, String name) throws Exception
    {

    }

    public void endSpotLight() throws Exception
    {
    }

    public void startGeometry() throws Exception
    {
    }

    public void endGeometry() throws Exception
    {
    }

    public void startSphere(float radius, String name) throws Exception
    {	
    	List<Primitive> primitives = new ArrayList<Primitive>();
    	primitives.add(new Sphere(new Vector3f(0,0,0), radius));
    	geometries.add(new Geometry(name, primitives));
    }

    public void endSphere() throws Exception
    {
    }

    public void startCylinder(float radius, float height, boolean capped, String name) throws Exception
    {
    	//TODO:height, radius
    	List<Primitive> primitives = new ArrayList<Primitive>();
    	primitives.addAll(ObjParser.parseObjFile("cylinder.obj"));
    	geometries.add(new Geometry(name, primitives));
    }

    public void endCylinder() throws Exception
    {
    }

    public void startCone(float radius, float height, boolean capped, String name) throws Exception
    {

    }

    public void endCone() throws Exception
    {
    }

    public void startTorus(float innerRadius, float outerRadius, String name) throws Exception
    {
    }

    public void endTorus() throws Exception
    {
    }

    public void startTeapot(float size, String name) throws Exception
    {
    	List<Primitive> primitives = new ArrayList<Primitive>();
    	primitives.addAll(ObjParser.parseObjFile("venus.obj"));
    	geometries.add(new Geometry(name, primitives));
    }

    public void endTeapot() throws Exception
    {
    }

    public void startIndexedTriangleSet(Point3f [] coordinates, Vector3f [] normals, TexCoord2f [] textureCoordinates, int [] coordinateIndices, int [] normalIndices, int [] textureCoordinateIndices, String name) throws Exception
    {
    }

    public void endIndexedTriangleSet() throws Exception
    {
    }

    public void startTextures() throws Exception
    {
    }

    public void endTextures() throws Exception
    {
    }

    public void startTexture(String src, String name, float scale, float offsetu, float offsetv) throws Exception
    {
    	Material texture = new Texture(src, name, scale, offsetu, offsetv);
    	materials.add(texture);
    }

    public void endTexture() throws Exception
    {
    }

    public void startMaterials() throws Exception
    {
    }

    public void endMaterials() throws Exception
    {
    }

    public void startDiffuseMaterial(Color3f color, String name) throws Exception
    {
    	materials.add(new Material(color, name));
    }

    public void endDiffuseMaterial() throws Exception
    {
    }

    public void startPhongMaterial(Color3f color, float shininess, String name) throws Exception
    {
    	materials.add(new PhongMaterial(color, shininess, name));
    }

    public void endPhongMaterial() throws Exception
    {
    }

    public void startLinearCombinedMaterial(String material1Name, float weight1, String material2Name, float weight2, String name) throws Exception
    {
    }

    public void endLinearCombinedMaterial() throws Exception
    {
    }

    public void startScene(String cameraName, String [] lightNames, Color3f background) throws Exception
    {
    	Camera camera = null;
    	for(Camera c : scene.getCameras()){
    		if(c.getName().equals(cameraName))
    			camera = c;
    	}
    	scene.setCurrentCamera(camera);
    	for(Light l : lights){
    		for(String lightName : lightNames){
    			if(l.getName().equals(lightName))
    				scene.addLight(l);
    		}
    	}
    	scene.setBackgroundColor(background);
    	scenegraph = new TransformNode(Matrix4f.identity(), null);
//    	Plane p = new Plane(new Vector3f(1,0,1), new Vector3f(0,1,0));
//    	List<Primitive> primitives = new ArrayList<Primitive>();
//    	primitives.add(p);
//    	Material planeMat = new Material(new Color3f(204f/255f, 230f/255f, 255f/255f),"planeMat");
//    	Shape s = new Shape(new Geometry("xyPlane", primitives), planeMat);
//		scenegraph = new ShapeNode(s, scenegraph);
//		scene.addShape(s);
    }

    public void endScene() throws Exception
    {
    	scenegraph.traverse(matrixStack);
    	scene.buildAccel();
    }

    public void startShape(String geometryName, String materialName, String textureName) throws Exception
    {
    	Geometry geometry = null;
    	for(Geometry g : geometries){
    		if(g.getName().equals(geometryName)){
    			geometry = g;
    		}
    	}
    	Material material = null;
    	if(!materialName.equals("")){
    		for(Material m : materials){
    			if(m.getName().equals(materialName)){
    				material = m;
    			}
    		}
    	}
    	if(!(material == null)){
    		Shape s = new Shape(geometry, material);
    		scenegraph = new ShapeNode(s, scenegraph);
    		scene.addShape(s);
    	}
    }

    public void endShape() throws Exception
    {
    	if(scenegraph.getParent() != null){
    		scenegraph = scenegraph.getParent();
    	}
    }

    public void startRotate(Vector3f axis, float angle) throws Exception
    {
    	//TransformNode previousTransform = scenegraph;
    	scenegraph = new TransformNode(Matrix4f.createRotationMatrix(axis, angle), scenegraph);
    }

    public void endRotate() throws Exception
    {
    	if(scenegraph.getParent() != null){
    		scenegraph = scenegraph.getParent();
    	}
    }

    public void startTranslate(Vector3f vector) throws Exception
    {
    	scenegraph = new TransformNode(Matrix4f.createTranslationMatrix(vector),scenegraph);
    }

    public void endTranslate() throws Exception
    {
    	if(scenegraph.getParent() != null){
    		scenegraph = scenegraph.getParent();
    	}
    }

    public void startScale(Vector3f scale) throws Exception
    {
    	scenegraph = new TransformNode(Matrix4f.createScaleMatrix(scale.x, scale.y, scale.z),scenegraph);
    }

    public void endScale() throws Exception
    {
    	if(scenegraph.getParent() != null){
    		scenegraph = scenegraph.getParent();
    	}
    }


	@Override
	public void startCubeMap(String negXFile, String negYFile, String negZFile,
			String posXFile, String posYFile, String posZFile) throws Exception {
		scene.setCubeMap(new CubeMap(negXFile, negYFile, negZFile, posXFile, posYFile, posZFile));
	}


	@Override
	public void endCubeMap() throws Exception{
	}


	@Override
	public void startObj(String src, String name) throws Exception {
		List<Primitive> primitives = new ArrayList<Primitive>();
    	primitives.addAll(ObjParser.parseObjFile(src));
    	geometries.add(new Geometry(name, primitives));
	}


	@Override
	public void endObj() throws Exception {
	}


	@Override
	public void startSpecularMaterial(Color3f color, String name,
			Color3f specularcolor) throws Exception {
		materials.add(new SpecularMaterial(color, name, specularcolor));
	}


	@Override
	public void endSpecularMaterial() throws Exception {
	}


	@Override
	public void startRefractionMaterial(String name, double refractionIndex,
			Color3f admittance) throws Exception {
		materials.add(new RefractionMaterial(name, refractionIndex, admittance));		
	}


	@Override
	public void endRefractionMaterial() throws Exception {
	}


	@Override
	public void startAreaLight(Vector3f position, float intensity,
			Color3f color, String name, Vector3f a, Vector3f b)
			throws Exception {
		lights.add(new AreaLight(position,intensity,color,name,a,b));	
	}


	@Override
	public void endAreaLight() throws Exception {
	}


	@Override
	public void startPlane(Vector3f point, Vector3f normal, String name)
			throws Exception {
		List<Primitive> primitives = new ArrayList<Primitive>();
    	primitives.add(new Plane(point, normal));
    	geometries.add(new Geometry(name, primitives));
	}


	@Override
	public void endPlane() throws Exception {
	}

}
